package com.javadaemon.rpgengine.util;

import java.io.FileInputStream;
import java.util.HashMap;
import java.util.Map;

import org.newdawn.slick.Animation;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;

import com.javadaemon.rpgengine.model.DIRECTION;
import com.javadaemon.rpgengine.screens.helpers.ImageRender;
import com.javadaemon.rpgengine.screens.helpers.ObjectRender;


/*
 Copyright (c) 2011, Morten Andersen
 All rights reserved.

 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright
 notice, this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright
 notice, this list of conditions and the following disclaimer in the
 documentation and/or other materials provided with the distribution.
 * The name of the author may not be used to endorse or promote products
 derived from this software without specific prior written permission.

 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 DISCLAIMED. IN NO EVENT SHALL MORTEN ANDERSEN BE LIABLE FOR ANY
 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * This class contains all logic for caching and accesssing external ressources, such as graphics, sound and music, in order to decouple this logic from the
 * internal game logic. All requests for ressources will use "logical" reference keys defined in the ResourceManager, to ensure that only the ResourceManager
 * needs to know the specific location and format of the external ressources.
 * 
 * @author Somnium
 * 
 */
public class ResourceManager {

    // The width and height of the tiles used.
    public static final int TILE_WIDTH = 16;
    public static final int TILE_HEIGHT = 16;

    private Map<ANIMATION, Animation> animations; // A Map containing a mapping
						  // between animation
						  // reference keys and Slick
						  // Animation instances
    private SpriteSheet graphics; // A reference to the Slick SpriteSheet used
				  // to contain the graphics.
    private Map<ANIMATION_SET, AnimationSet> animationSets; // A Map containing
							    // a mapping between
							    // animation set
							    // reference keys
							    // and AnimationSet
							    // instances.
    private Map<OBJECT_RENDER, ObjectRender> renders;

    /**
     * This enum contains reference keys for the map images used, and their location on the file.
     * 
     * @author Somnium
     * 
     */
    public enum MAP {
	MAP001("TestWorld");

	private final String mapname;

	private MAP(String mapname) {
	    this.mapname = mapname;
	}
	public String getImageFile() {
	    return "data/maps/"+mapname+"/map.png";
	}
	public String getObjectsFile() {
	    return "data/maps/"+mapname+"/objects.xml";
	}
    }
    
    public enum OBJECT_RENDER {
	SMALL_ROOF_RIGHT_END_RENDER,
	SMALL_ROOF_MIDDLE_RENDER,
	SMALL_ROOF_LEFT_END_RENDER,
	SMALL_HOUSE_RIGHT_END_RENDER,
	SMALL_HOUSE_LEFT_END_RENDER,
	DOOR_RENDER,
	FENCE_RENDER,
	SIGN_RENDER;
    }

    /**
     * This enum contains reference keys for the images used, and their location in the sprite sheet.
     * 
     * @author Somnium
     */
    public enum IMAGE {
	TERRAIN_GRASS_SMALL_TREE(0, 0),
	TERRAIN_GRASS(1, 0),
	TERRAIN_GROUND(2, 0),
	TERRAIN_BIGSTUMP(3, 0),
	TERRAIN_BIGTOP(4, 0),

	MAN_WALK_NORTH_1(0, 2),
	MAN_WALK_NORTH_2(1, 2),
	MAN_WALK_NORTH_3(2, 2),
	MAN_WALK_EAST_1(3, 2),
	MAN_WALK_EAST_2(4, 2),
	MAN_WALK_SOUTH_1(5, 2),
	MAN_WALK_SOUTH_2(6, 2),
	MAN_WALK_SOUTH_3(7, 2),
	MAN_WALK_WEST_1(8, 2),
	MAN_WALK_WEST_2(9, 2),

	SMALL_ROOF_RIGHT_END(0, 4),
	SMALL_ROOF_MIDDLE(1, 4),
	SMALL_ROOF_LEFT_END(2, 4),
	SMALL_HOUSE_RIGHT_END(3, 4),
	SMALL_HOUSE_LEFT_END(4, 4),
	DOOR(5, 4),
	FENCE(6, 4),
	SIGN(7, 4);

	private IMAGE(int column, int row) {
	    this.SHEET_COLUMN = column;
	    this.SHEET_ROW = row;
	}

	private final int SHEET_COLUMN, SHEET_ROW;

	public int getSheetColumn() {
	    return SHEET_COLUMN;
	}

	public int getSheetRow() {
	    return SHEET_ROW;
	}
    }

    /**
     * This enum contains reference keys for the animations used, as well as which images the animations consists of.
     * 
     * @author Somnium
     */
    public enum ANIMATION {
	KNIGHT_WALK_NORTH(
		210,
		true,
		IMAGE.MAN_WALK_NORTH_2,
		IMAGE.MAN_WALK_NORTH_1,
		IMAGE.MAN_WALK_NORTH_3),
	KNIGHT_WALK_SOUTH(
		210,
		true,
		IMAGE.MAN_WALK_SOUTH_2,
		IMAGE.MAN_WALK_SOUTH_1,
		IMAGE.MAN_WALK_SOUTH_3),
	KNIGHT_WALK_WEST(
		160,
		true,
		IMAGE.MAN_WALK_WEST_1,
		IMAGE.MAN_WALK_WEST_2),
	KNIGHT_WALK_EAST(
		160,
		true,
		IMAGE.MAN_WALK_EAST_1,
		IMAGE.MAN_WALK_EAST_2),

	// Character animations
	// These are kept seperate due to animation re-use. We don't want
	// moon-walking characters, do we?
	CHARACTER_WALK_NORTH(
		210,
		true,
		IMAGE.MAN_WALK_NORTH_2,
		IMAGE.MAN_WALK_NORTH_1,
		IMAGE.MAN_WALK_NORTH_3),
	CHARACTER_WALK_SOUTH(
		210,
		true,
		IMAGE.MAN_WALK_SOUTH_2,
		IMAGE.MAN_WALK_SOUTH_1,
		IMAGE.MAN_WALK_SOUTH_3),
	CHARACTER_WALK_WEST(
		160,
		true,
		IMAGE.MAN_WALK_WEST_1,
		IMAGE.MAN_WALK_WEST_2),
	CHARACTER_WALK_EAST(
		160,
		true,
		IMAGE.MAN_WALK_EAST_1,
		IMAGE.MAN_WALK_EAST_2), ;

	private final int length;
	private final IMAGE[] frames;
	private final boolean pingpong;

	private ANIMATION(int length, boolean pingpong, IMAGE... frames) {
	    this.length = length;
	    this.pingpong = pingpong;
	    this.frames = frames;
	}
    }

    /**
     * This enum contains reference keys for the animation sets used.
     * 
     * @author Somnium
     * 
     */
    public enum ANIMATION_SET {
	PLAYER_ANIMATIONS,
	CHARACTER_ANIMATIONS, ;
    }

    /**
     * Gets the singleton instance of the ressource manager, or instantiates a new ressource manager if no instance exists.
     * 
     * @return The ressource manager instance
     * @throws SlickException
     */
    public static ResourceManager getRessourceManager() throws SlickException {
	if (singleton == null) {
	    singleton = new ResourceManager();
	}

	return singleton;
    }

    private static ResourceManager singleton = null; // The singleton reference.

    /**
     * Constructor - note that this is private, as the singleton pattern is used.
     * 
     * @throws SlickException
     */
    private ResourceManager() throws SlickException {
	init(); // Initialize the resources.
    }

    /**
     * This method pre-caches the external resources.
     * 
     * @throws SlickException
     */
    private void init() throws SlickException {
	// Init graphics
	initGraphics();
    }

    /**
     * Initialize the graphics and related resources (animations, animation sets) used by the program.
     * 
     * @throws SlickException
     */
    private void initGraphics() throws SlickException {
	// Prepare the sprite sheet for use
	graphics = new SpriteSheet("res/sheets/sheet01.png", TILE_WIDTH,
		TILE_HEIGHT);

	// Create animations
	animations = new HashMap<ANIMATION, Animation>();
	for (ANIMATION animationEnum : ANIMATION.values()) // Loop through all
							   // animation keys,
							   // and initialize
							   // their
							   // corresponding
							   // Slick Animation
							   // instances
	{
	    int numberOfFrames = animationEnum.frames.length;
	    Image[] frames = new Image[numberOfFrames];
	    for (int i = 0; i < numberOfFrames; i++) {
		IMAGE frameReference = animationEnum.frames[i];
		frames[i] = graphics.getSubImage(frameReference.SHEET_COLUMN,
			frameReference.SHEET_ROW);
	    }

	    Animation animation = new Animation(frames, animationEnum.length);
	    animation.setPingPong(animationEnum.pingpong);
	    animations.put(animationEnum, animation); // Add the new animation
						      // to the animation map,
						      // mapped to the key
	}
	
	// Create the renders
	/**
	 * There is very good reason to use a different file for mapping these over hardcoding them. 
	 * Once the list gets filled hardcoding will be much less fun for programmers to swin around.
	 */
	renders = new HashMap<OBJECT_RENDER, ObjectRender>();
	renders.put(OBJECT_RENDER.DOOR_RENDER, new ImageRender(IMAGE.DOOR));
	renders.put(OBJECT_RENDER.FENCE_RENDER, new ImageRender(IMAGE.FENCE));
	renders.put(OBJECT_RENDER.SIGN_RENDER, new ImageRender(IMAGE.SIGN));
	renders.put(OBJECT_RENDER.SMALL_HOUSE_LEFT_END_RENDER, new ImageRender(IMAGE.SMALL_HOUSE_LEFT_END));
	renders.put(OBJECT_RENDER.SMALL_HOUSE_RIGHT_END_RENDER, new ImageRender(IMAGE.SMALL_HOUSE_RIGHT_END));
	renders.put(OBJECT_RENDER.SMALL_ROOF_MIDDLE_RENDER, new ImageRender(IMAGE.SMALL_ROOF_MIDDLE));
	renders.put(OBJECT_RENDER.SMALL_ROOF_LEFT_END_RENDER, new ImageRender(IMAGE.SMALL_ROOF_LEFT_END));
	renders.put(OBJECT_RENDER.SMALL_ROOF_RIGHT_END_RENDER, new ImageRender(IMAGE.SMALL_ROOF_RIGHT_END));

	// Create animation sets
	// For now, these are hardcoded
	animationSets = new HashMap<ANIMATION_SET, AnimationSet>();
	
	// Player animations
	AnimationSet playerAnimations = new AnimationSet();
	playerAnimations.putAnimation(DIRECTION.NORTH,
		ANIMATION.KNIGHT_WALK_NORTH);
	playerAnimations.putAnimation(DIRECTION.SOUTH,
		ANIMATION.KNIGHT_WALK_SOUTH);
	playerAnimations.putAnimation(DIRECTION.WEST,
		ANIMATION.KNIGHT_WALK_WEST);
	playerAnimations.putAnimation(DIRECTION.EAST,
		ANIMATION.KNIGHT_WALK_EAST);
	animationSets.put(ANIMATION_SET.PLAYER_ANIMATIONS, playerAnimations);

	// Character animations
	AnimationSet characterAnimations = new AnimationSet();
	characterAnimations.putAnimation(DIRECTION.NORTH,
		ANIMATION.CHARACTER_WALK_NORTH);
	characterAnimations.putAnimation(DIRECTION.SOUTH,
		ANIMATION.CHARACTER_WALK_SOUTH);
	characterAnimations.putAnimation(DIRECTION.WEST,
		ANIMATION.CHARACTER_WALK_WEST);
	characterAnimations.putAnimation(DIRECTION.EAST,
		ANIMATION.CHARACTER_WALK_EAST);
	animationSets.put(ANIMATION_SET.CHARACTER_ANIMATIONS,
		characterAnimations);
    }

    /**
     * Get a specific Slick Animation from the cache, using an ANIMATION key.
     * 
     * @param animation
     * @return
     */
    public Animation getAnimation(ANIMATION animation) {
	return animations.get(animation);
    }

    /**
     * Get a specific animation set from the cache, using an ANIMATION_SET key.
     * 
     * @param animationSet
     * @return
     */
    public AnimationSet getAnimationSet(ANIMATION_SET animationSet) {
	return animationSets.get(animationSet);
    }
    
    public ObjectRender getObjectRender(OBJECT_RENDER render) {
	return renders.get(render);
    }

    /**
     * Get the sprite sheet containing the graphics used.
     * 
     * @return
     */
    public SpriteSheet getGraphics() {
	return graphics;
    }
}
